"""
========================================================================
FILE: mesh.py

LAST MODIFIED: 22 September 2009

DESCRIPTION: 
class for holding information about the mesh topology
========================================================================
"""
"""
BEGIN LICENSE BLOCK 
Version: MPL 1.1/GPL 2.0/LGPL 2.1

The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.

The Original Code is fieldwork.

The Initial Developer of the Original Code is Ju Zhang.
Portions created by the Initial Developer are Copyright (C) 2009
the Initial Developer. All Rights Reserved.

Contributor(s):

Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.

END LICENSE BLOCK
"""
import element_types

class mesh_ensemble:
	""" Class for holding information about field topology
	"""
	
	def __init__( self, name, dimensions, debug = 0 ):
		self.name = name
		self.dimensions = dimensions		# number of dimensions of the mesh topology
		self.element_counter = 0			# counter of number of elements
		self.hanging_point_counter = 0		# counter for number of hanging points
		self.number_of_points = None		# number of unique ensemble points
		
		self.elements = {}					# { element_number: element, element_number: sub mesh_ensemble }
		
		self.connectivity = {}				# { ( element_num, point_num, ?? ) : [(element_num, point_num, ??), ...]] } !! needs to incorporate topological information: xi orientation and element CS density
		self.hanging_points = {}			# { hanging_point number: hanging_point, ... }
		self.element_points = {}			# { element_number: [ element_points ] } maps element to its element points
		
		self.is_element = False
		self.debug = debug
	
	#==================================================================#	
	def add_element( self, element ):
		""" adds an element to the mesh. Element can either be an element
		object or another mesh_ensemble. Element is added to the elements
		dict as a value with an element number. get_number_of_points is called
		to get the number of entries to add to connectivity and the 
		element_points map.
		
		returns element number for convenience.
		"""
		
		self.elements[ self.element_counter ] = element
		self.element_points[self.element_counter] = []
		for p in range( 0, element.get_number_of_ensemble_points() ):
			self.connectivity.update( { ( self.element_counter, p ): []} )
			self.element_points[ self.element_counter ].append( ( self.element_counter, p ) )
			
		self.element_counter += 1
		
		return (self.element_counter - 1)
	
	#==================================================================#
	def remove_element( self, element_number ):
		""" removes element element_number from the elements dict, and all
		entries the element from connectivity. Decreases element
		counter by 1.
		"""
		
		# check for valid element number
		if element_number not in self.elements.keys():
			print 'ERROR: topology.remove_element: element', element_number, 'does not exist'
			return None
			
		# removed from elements dict
		del self.elements[element_number]
		
		# removed element points from connectivity
		element_points = self.element_points[ element_number ]
		# for each element point
		for point in element_points:
			# get points its connected to
			connected_points = self.connectivity[ point ]
			# remove this element point for its connected points
			for connected_point in connected_points:
				self.connectivity[ connected_point ].remove( point )
			# removed current element point entry
			del self.connectivity[ point ]
			
		# removed element points map entry
		del self.element_points[ element_number ]
		
		# decrease element counter
		self.element_counter -= 1
		
		return 1
			
	
	#==================================================================#	
	def get_number_of_ensemble_points( self ):
		""" calculates the number of unique ensemble points in the mesh
		by going through the connectivity dict.
		"""
		
		gp = 0	# global points number
		assigned = []	# list of element points already assigned a global number
		
		# account for points connected to hanging nodes
		#~ if self.hanging_points:
			#~ for points in self.hanging_point_connectivity.values():
				#~ assigned += points
		
		# loop through each element point in the connectivity dict
		element_points = self.connectivity.keys()
		element_points.sort()
		if self.debug:
			print 'sorted element points:', element_points
		for element_point in element_points:
			# check if point has already been assigned a global number
			if (element_point not in assigned):
				# record element points assigned
				assigned += [element_point] + self.connectivity[element_point]
				if self.debug:
					print 'assigned:', assigned
				
				# if not a hanging point, count as an ensemble point
				if element_point[0] != -1:
					gp += 1	
		
		self.number_of_points = gp
		
		return self.number_of_points
		
	#==================================================================#
	def connect( self, common_points ):
		""" define how the elements are connected
		common_points is a list of tuples of element nodes, or coordinates at the 
		same ensemble mesh position: 
		[ (element_num, node_num1, ??), (element_num2, (element_xi), ??), ...]
		
		!! should incorporate information about element coord orientation and coord density here !!
		!! should be able to connect to any position in an element, not only to other element points => hanging nodes handled naturally !!
		!! => connectivity contains extra topology info!!
		""" 
		
		for point in common_points:
			
			if point not in self.connectivity.keys():
				print 'ERROR: toplogy.connect: point', point, 'does not exist'
				avail_keys = self.connectivity.keys()
				avail_keys.sort()
				print 'available points to connect:\n', avail_keys
				return None
			
			# append other points to its list
			other_points = list( common_points[:] )
			other_points.remove( point )
			
			for other_point in other_points:
				if other_point not in self.connectivity[point]:
					self.connectivity[point].append( other_point )
	
		return 1
	
	#==================================================================#
	def connect_to_hanging_point( self, host_element, element_coordinates, connected_points ):
		""" host_element: int
			element_coordinates: [xi2, xi2,...]
			connected_points: [ (element_num, point_num), ...]
		
		creates a hanging point in host_element at element_coordinates,
		and connects connected_points to the hanging point
		"""
		
		# create hanging point
		if self._add_hanging_point( host_element, element_coordinates ):
			# add entry to connectivity
			connected_points.append( (-1, self.hanging_point_counter - 1 ) )
			# connect
			self.connect( connected_points )
			return 1
		else:
			print 'ERROR: topology.connect_to_hanging_point: failed to add hanging_point'
			return None
	
	#==================================================================#	
	def _add_hanging_point( self, host_element, element_coordinates):
		""" adds a hanging point to the mesh in host_element at 
		element_coordinates
		"""
		
		# check for valid element and number of coordinates
		# will have trouble with child elements with different dimensionality
		if host_element in self.elements.keys():
			if len( element_coordinates ) == self.elements[host_element].dimensions:
				# instantiate hanging_point
				hanging_point = hanging_point( host_element, element_coordinates )
				
				# add hanging point to hanging_points dict and connectivity under element -1
				self.hanging_points[self.hanging_point_counter] = hanging_point
				self.connectivity[ (-1, self.hanging_point_counter) ] = []
				self.hanging_point_counter += 1
				
				return 1
			else:
				print 'ERROR: topology.add_hanging_point: incorrect number of element coordinates. Need', self.elements[host_element].dimensions,', got', len( element_coordinates )
				return None
		else:
			print 'ERROR: topology.add_hanging_point: element', host_element, 'does not exist'
			return None

	
	#==================================================================#	
	#~ def _get_element_points( self, element_number ):
		#~ # returns a list of tuples containing all elements of element
		#~ # element_number
		#~ 
		#~ # check for valid element
		#~ if element_number not in self.elements.keys():
			#~ print 'ERROR: topology._get_element_points: element', element_number, 'does not exist'
			#~ return 0
		#~ 
		#~ element_points = []	
		#~ ckeys = self.connectivity.keys()
		#~ ckeys.sort
		#~ ckeys_iter = iter(ckeys)
		#~ done = 0
		#~ 
		#~ # look through sorted connectivity keys until 1st element point
		#~ # of element
		#~ while not done:
			#~ key = ckeys_iter.next()
			#~ if key[0] = element_number:
				#~ element_points.append( key )
				
				
			
	#==================================================================#	
	#~ def add_local_element( self, type ):
		#~ """ adds a element to the mesh. Type is a string of the of the
		#~ type of element. The element is appended to the 
		#~ self.local_elements dict with the current element_counter value. 
		#~ List of nodes in that element are appended to the connectivity 
		#~ dict. self.element_counter is increased by 1
		#~ """
		#~ # instantiate element
		#~ element = element_types.create_element( type )
		#~ 
		#~ if element:
			#~ # add to local_elements dict
			#~ self.elements[ self.element_counter ] = element
			#~ 
			#~ # add nodes as keys to connectivity
			#~ for p in range(0, element.number_of_points ):
				#~ self.connectivity.update( { [element_counter, p]: []} )
		#~ 
			#~ element_number += 1
			#~ return 1
		#~ else:
			#~ print 'ERROR: mesh_ensemble.add_element failed'
			#~ return 0

	#==================================================================#	
	#~ def add_sub_mesh( self, sub_mesh ):
		#~ """ sub_mesh is an ensemble_mesh instance. it is given an element
		#~ number and appended to the self.elements dict. Queries the sub_mesh's
		#~ get_number_of_points() to get number of points exposed to the parent.
		#~ """
		#~ 
		#~ 
		#~ # add sub mesh elements/element_points to connectivity dict using
		#~ # the connectivity in the sub_mesh
		#~ e_start = len( self.elements )	# element number of elements in the sub mesh are padded by this number
		#~ 
		#~ sub_mesh_elements = sub_mesh.connectivity.keys()
		#~ sub_mesh_elements.sort()
		#~ 
		#~ for key in sub_mesh_elements:
			#~ 
			#~ sub_element = key[0]
			#~ sub_point = key [1]
			#~ 
			#~ # add new connectivity key
			#~ self.connectivity.update( { ( sub_element + e_start, sub_point ): [] }
			#~ # add values to new connectivity key
			#~ for ( e, p ) in sub_mesh.connectivity[key]:
				#~ self.connectivity[ sub_element + e_start, sub_point ].append( ( e + e_start, p ) )
				#~ 
			#~ # map element number to its sub_mesh, and sub mesh element number
			#~ self.sub_mesh_element_map.update( { ( sub_element + e_start, sub_point ): (sub_mesh_number, sub_element) } )
	#~ 
		#~ return 1
		

# hanging point object. Stored information about its host_element and
# host element coordinates	
class hanging_point:
	""" Class for holding information about a hanging_point (node)
	"""
	
	def __init__(self, host_element, element_coordinates):
		self.host_element = host_element
		self.element_coordinates = tuple( element_coordinates )
		
	def get_host_element( self ):
		return self.host_element
		
	def get_element_coordinates( self ):
		return self.element_coordinates
